"""Verify the standalone PDF, embedded fonts, text, page dimensions and evidence.""" import hashlib import json import unicodedata from pathlib import Path from pypdf import PdfReader root = Path(__file__).resolve().parents[1] output = root / 'docs/teaching/rtcp-five-axis' pdf = output / '五轴机床与RTCP-教学课件.pdf' reader = PdfReader(pdf) outline = json.loads((output / 'outline.json').read_text()) layout = json.loads((output / 'verification/layout.json').read_text()) numerics = json.loads((output / 'numeric-evidence.json').read_text()) assert len(reader.pages) == len(outline) == 31 assert numerics['status'] == 'verified' and not layout['errors'] assert all(not p['clipped'] and not p['content_overflow'] for p in layout['pages']) pages = [] fonts = {} for index, page in enumerate(reader.pages): text = unicodedata.normalize('NFKC', page.extract_text()) assert len(text) > 100, (index, len(text)) assert abs(float(page.mediabox.width) / float(page.mediabox.height) - 16/9) < .001 assert str(index+1).zfill(2) in text assert '\ufffd' not in text for font in page['/Resources'].get('/Font', {}).values(): value = font.get_object() descendants = value.get('/DescendantFonts', [value]) for item in descendants: descriptor = item.get_object().get('/FontDescriptor') if descriptor: descriptor = descriptor.get_object() # Chrome embeds CFF CJK glyphs as Type 3 CharProcs instead of # a FontFile stream; both forms are self-contained PDF fonts. type3 = item.get_object().get('/Subtype') == '/Type3' and bool(item.get_object().get('/CharProcs')) embedded = type3 or any(key in descriptor for key in ['/FontFile', '/FontFile2', '/FontFile3']) name = str(descriptor.get('/FontName')) fonts[name] = embedded pages.append({'page': index+1, 'characters': len(text), 'width_pt': float(page.mediabox.width), 'height_pt': float(page.mediabox.height)}) assert fonts and all(fonts.values()), fonts assert any('CJK' in font for font in fonts), fonts assert len(reader.outline) >= 31 report = { 'report_id': 'rtcp-teaching-pdf-v1', 'status': 'verified', 'pages': pages, 'pdf': pdf.name, 'bytes': pdf.stat().st_size, 'sha256': hashlib.sha256(pdf.read_bytes()).hexdigest(), 'bookmarks': len(reader.outline), 'embedded_fonts': fonts, 'numeric_report_id': numerics['report_id'], 'wasm_sha256': numerics['wasm_sha256'], 'layout_report': 'verification/layout.json', 'evidence': [{'path': str(p.relative_to(output)), 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in [output/'course.html', output/'numeric-evidence.json', output/'outline.json', output/'verification/layout.json', *sorted((output/'assets').glob('*.png'))]], } (output / 'verification/acceptance.json').write_text(json.dumps(report, ensure_ascii=False, indent=2)+'\n') print(json.dumps({'report_id':report['report_id'], 'status':report['status'], 'pages':len(pages), 'bytes':report['bytes'], 'fonts':len(fonts), 'bookmarks':report['bookmarks']},ensure_ascii=False))