mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-22 23:08:53 +02:00
tpye3 text edit init
This commit is contained in:
443
docs/pdf_json_type3_fonts.md
Normal file
443
docs/pdf_json_type3_fonts.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# PDF JSON Type3 Font System
|
||||
|
||||
## Overview
|
||||
|
||||
The PDF JSON editor needs to handle **Type3 fonts** - custom vector fonts embedded in PDFs that don't follow standard font formats. These are common in PDFs generated by Matplotlib, LaTeX, scientific papers, and presentation tools.
|
||||
|
||||
When converting a PDF to JSON for editing, Type3 fonts present two challenges:
|
||||
1. **No Unicode mapping** - Character codes don't map to standard Unicode characters
|
||||
2. **Custom glyphs** - Each font contains vector drawing instructions unique to that PDF
|
||||
|
||||
This document explains how the system handles Type3 fonts during the full PDF → JSON → PDF workflow.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Flow
|
||||
|
||||
### PDF → JSON Conversion Flow
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Input PDF │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ PDFBox Parsing │
|
||||
│ - Extract text positions │
|
||||
│ - Identify fonts │
|
||||
└──────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Font Detection │
|
||||
│ Is this a Type3 font? │
|
||||
└──────┬──────────────────────────┘
|
||||
│
|
||||
├─── YES (Type3) ───────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ Type3FontConversion │ │ Extract Type3 Metadata │
|
||||
│ Service │ │ - Glyph outlines (paths) │
|
||||
│ │ │ - Character codes │
|
||||
│ 1. Calculate signature │ │ - Font matrix │
|
||||
│ 2. Match against │ │ - Bounding boxes │
|
||||
│ library │ └──────────────────────────────┘
|
||||
└──────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Library Match? │
|
||||
└──────┬───────────────────────────────┘
|
||||
│
|
||||
├─── FOUND ─────────────────────┐
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌─────────────────────────────┐
|
||||
│ │ Load Pre-built Font │
|
||||
│ │ - TTF/OTF from library │
|
||||
│ │ - Full Unicode mappings │
|
||||
│ │ - Web + PDF payloads │
|
||||
│ └──────────┬──────────────────┘
|
||||
│ │
|
||||
├─── NOT FOUND ──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Store Type3 Metadata in JSON │
|
||||
│ - type3Glyphs: [{charCode, unicode, │
|
||||
│ glyphName, outline}] │
|
||||
│ - Original char codes preserved │
|
||||
│ - Font marked as Type3 │
|
||||
└──────┬───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ PdfJsonDocument Output │
|
||||
│ { │
|
||||
│ fonts: [{ │
|
||||
│ id: "F1", │
|
||||
│ baseName: "ABCD+DejaVuSans", │
|
||||
│ subtype: "Type3", │
|
||||
│ type3Glyphs: [...], │
|
||||
│ conversionCandidates: [{ │
|
||||
│ strategyId: "type3-library", │
|
||||
│ status: "SUCCESS", │
|
||||
│ pdfProgram: "base64...", │
|
||||
│ glyphCoverage: [65,66,67...] │
|
||||
│ }] │
|
||||
│ }], │
|
||||
│ textElements: [{ │
|
||||
│ text: "Hello", │
|
||||
│ fontId: "F1", │
|
||||
│ charCodes: [72,101,108,108,111]│
|
||||
│ }] │
|
||||
│ } │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### JSON → PDF Conversion Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Input JSON (edited by user) │
|
||||
└──────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Load Fonts from JSON │
|
||||
│ - Check for conversionCandidates │
|
||||
│ - Check for type3Glyphs │
|
||||
└──────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Has conversionCandidates? │
|
||||
└──────┬──────────────────────────────┘
|
||||
│
|
||||
├─── YES (Library Match) ───────┐
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌─────────────────────────────┐
|
||||
│ │ Load from Candidate │
|
||||
│ │ 1. Decode base64 pdfProgram│
|
||||
│ │ 2. Create PDType0Font │
|
||||
│ │ 3. Embed in new PDF │
|
||||
│ └──────────┬──────────────────┘
|
||||
│ │
|
||||
├─── NO (Use Type3 Metadata) ───┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Text Rendering Strategy │
|
||||
│ - Normalized Type3 fonts: │
|
||||
│ Use original text (font has │
|
||||
│ Unicode mappings) │
|
||||
│ │
|
||||
│ - Actual Type3 fonts: │
|
||||
│ Use charCodes array │
|
||||
│ │
|
||||
│ - Other fonts: │
|
||||
│ Standard encoding │
|
||||
└──────┬───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Generate PDF Content Streams │
|
||||
│ - Set font: /F1 12 Tf │
|
||||
│ - Position text: x y Td │
|
||||
│ - Show text: (encoded) Tj │
|
||||
└──────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Output PDF │
|
||||
│ - Fonts embedded correctly │
|
||||
│ - Text renders with proper glyphs │
|
||||
│ - Preserves visual appearance │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. Type3 Font Signature Matching
|
||||
|
||||
**Location:** `Type3FontSignatureCalculator.java`
|
||||
|
||||
**Purpose:** Creates a unique fingerprint of a Type3 font based on its glyph shapes.
|
||||
|
||||
**How it works:**
|
||||
```java
|
||||
// 1. Extract glyph outlines from Type3 font
|
||||
List<Type3GlyphOutline> glyphs = extractor.extractGlyphs(document, font, fontId, pageNumber);
|
||||
|
||||
// 2. Normalize and hash the shapes
|
||||
String signature = calculator.calculateSignature(glyphs);
|
||||
// Result: "sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e..."
|
||||
|
||||
// 3. Look up in library
|
||||
Optional<Match> match = library.findBySignature(signature);
|
||||
```
|
||||
|
||||
**Signature includes:**
|
||||
- Glyph outline paths (curves, lines)
|
||||
- Glyph bounding boxes
|
||||
- Advance widths
|
||||
- Character code mappings
|
||||
|
||||
### 2. Type3 Font Library
|
||||
|
||||
**Location:** `app/core/src/main/resources/type3/library/`
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
type3/library/
|
||||
├── index.json # Font metadata and signatures
|
||||
├── catalogue.json # Quick lookup of common fonts
|
||||
└── fonts/
|
||||
├── dejavu/
|
||||
│ ├── DejaVuSans.ttf
|
||||
│ ├── DejaVuSans-Bold.ttf
|
||||
│ └── DejaVuSans-Oblique.ttf
|
||||
├── cm/ # Computer Modern (LaTeX)
|
||||
│ ├── cmr10.ttf
|
||||
│ ├── cmmi10.ttf
|
||||
│ └── cmsy10.ttf
|
||||
├── stix/ # Scientific symbols
|
||||
│ └── STIXSizeThreeSym-Regular.otf
|
||||
└── scp/ # Monospace
|
||||
└── SauceCodeProNerdFont-Regular.ttf
|
||||
```
|
||||
|
||||
**index.json format:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "dejavu-sans-bold",
|
||||
"label": "DejaVu Sans Bold",
|
||||
"aliases": [
|
||||
"DejaVuSans-Bold",
|
||||
"EVICAO+DejaVuSans-Bold",
|
||||
"dejavusans-bold"
|
||||
],
|
||||
"signatures": [
|
||||
"sha256:a1b2c3d4...",
|
||||
"sha256:e5f6g7h8..."
|
||||
],
|
||||
"pdfProgram": {
|
||||
"resource": "type3/library/fonts/dejavu/DejaVuSans-Bold.ttf",
|
||||
"format": "ttf"
|
||||
},
|
||||
"glyphCoverage": [32, 33, 65, 66, 67, ...],
|
||||
"source": "DejaVu Fonts 2.37"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 3. Normalized vs Actual Type3 Fonts
|
||||
|
||||
The system handles two types of Type3 fonts differently:
|
||||
|
||||
**Normalized Type3 Fonts:**
|
||||
- Original PDF has Type3 font
|
||||
- Matched against library
|
||||
- Replaced with standard TTF/OTF
|
||||
- Font object is `PDType0Font` (not `PDType3Font`)
|
||||
- Has proper Unicode mappings
|
||||
|
||||
**Actual Type3 Fonts:**
|
||||
- Original PDF has Type3 font
|
||||
- No library match found
|
||||
- Keeps Type3 glyph data in JSON
|
||||
- Font object is `PDType3Font`
|
||||
- Uses character codes instead of Unicode
|
||||
|
||||
**Rendering logic (PdfJsonConversionService.java:2411-2463):**
|
||||
```java
|
||||
boolean isNormalizedType3 = !(run.font() instanceof PDType3Font)
|
||||
&& runFontModel != null
|
||||
&& runFontModel.getType3Glyphs() != null
|
||||
&& !runFontModel.getType3Glyphs().isEmpty();
|
||||
|
||||
if (isNormalizedType3) {
|
||||
// Font has Unicode mappings, use text directly
|
||||
contentStream.showText(run.text());
|
||||
} else {
|
||||
// Use raw byte encoding (for Type3 or other fonts)
|
||||
byte[] encoded = encodeTextWithFont(run.font(), fontModel, run.text(), charCodes);
|
||||
contentStream.showText(new String(encoded, StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Character Code Preservation
|
||||
|
||||
**Why needed:** Type3 fonts often lack ToUnicode mappings. We preserve the original character codes so text can be reconstructed.
|
||||
|
||||
**Storage in JSON:**
|
||||
```json
|
||||
{
|
||||
"text": "Hello",
|
||||
"fontId": "F1",
|
||||
"charCodes": [72, 101, 108, 108, 111]
|
||||
}
|
||||
```
|
||||
|
||||
**Extraction (PDF → JSON):**
|
||||
```java
|
||||
// TextCollectingStripper.java:4431-4443
|
||||
if (pdfont instanceof PDType3Font) {
|
||||
int[] codes = position.getCharacterCodes();
|
||||
if (codes != null && codes.length > 0) {
|
||||
element.setCharCodes(Arrays.stream(codes)
|
||||
.boxed()
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Font Embedding Strategies
|
||||
|
||||
When converting JSON → PDF, fonts are embedded based on their type:
|
||||
|
||||
| Font Type | Strategy | Implementation |
|
||||
|-----------|----------|----------------|
|
||||
| **Normalized Type3** | Load TTF/OTF from library, embed as PDType0Font | `conversionCandidates[0].pdfProgram` |
|
||||
| **Standard fonts** | Use system fonts or embedded fonts from original | PDFBox standard loading |
|
||||
| **CFF/Type1C fonts** | Wrap as OpenType-CFF for browser compatibility | Optional Python converter |
|
||||
| **Actual Type3** | Keep original Type3 definition | Preserve from original PDF |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
**settings.yml:**
|
||||
```yaml
|
||||
processing:
|
||||
pdf-json:
|
||||
fonts:
|
||||
type3:
|
||||
library:
|
||||
enabled: true
|
||||
index: classpath:/type3/library/index.json
|
||||
```
|
||||
|
||||
**Environment variables:**
|
||||
```bash
|
||||
# Disable Type3 library matching
|
||||
STIRLING_PDF_JSON_TYPE3_LIBRARY_ENABLED=false
|
||||
|
||||
# Use custom library
|
||||
STIRLING_PDF_JSON_TYPE3_LIBRARY_INDEX=file:/path/to/custom/index.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### View Type3 Font Information
|
||||
|
||||
**Backend logs** show signature matching:
|
||||
```
|
||||
[TYPE3] Strategy type3-library finished with status SUCCESS
|
||||
(message: Matched DejaVu Sans Bold via alias:dejavusans-bold)
|
||||
for font 1:F2
|
||||
|
||||
[TYPE3-RUNTIME] Loading library font F2 WITHOUT subsetting
|
||||
(full glyph set) from candidate:type3-library:pdfProgram
|
||||
```
|
||||
|
||||
### Check JSON Output
|
||||
|
||||
Look for `type3Glyphs` in font definitions:
|
||||
```json
|
||||
{
|
||||
"id": "F1",
|
||||
"baseName": "BMQQDV+DejaVuSans",
|
||||
"subtype": "Type3",
|
||||
"type3Glyphs": [
|
||||
{
|
||||
"charCode": 65,
|
||||
"glyphName": "A",
|
||||
"unicode": 65,
|
||||
"advanceWidth": 684,
|
||||
"bbox": [0, 0, 684, 729],
|
||||
"outline": "M 72 0 L ..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Test Signature Calculation
|
||||
|
||||
Use the CLI tool to analyze any PDF:
|
||||
```bash
|
||||
./gradlew :proprietary:type3SignatureTool \
|
||||
--args="--pdf sample.pdf --output analysis.json --pretty"
|
||||
```
|
||||
|
||||
Output shows all Type3 fonts with their signatures and glyph coverage.
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Memory Management
|
||||
|
||||
- **Type3 glyph data** can be large (detailed vector paths)
|
||||
- **Font subsetting** not used for library fonts (full glyph set loaded)
|
||||
- **Caching:** Library fonts are loaded once and cached per conversion
|
||||
|
||||
### File Size Impact
|
||||
|
||||
- **JSON size:** Type3 glyph data adds ~5-50KB per font
|
||||
- **PDF size:** Embedding TTF/OTF fonts adds ~50-500KB per font
|
||||
- **Optimization:** Use library matching to avoid storing raw Type3 data
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Library coverage:** Only common Type3 fonts are in the library
|
||||
- Matplotlib (DejaVu Sans family)
|
||||
- LaTeX (Computer Modern)
|
||||
- STIX Math symbols
|
||||
|
||||
2. **Glyph accuracy:** Signature matching assumes exact glyph shapes
|
||||
- Slight variations may not match
|
||||
- Subset fonts may have different signatures
|
||||
|
||||
3. **Unicode mapping:** Unmatched Type3 fonts lose Unicode text
|
||||
- Character codes preserved but not searchable
|
||||
- Copy/paste may not work correctly
|
||||
|
||||
4. **No runtime synthesis:** Unlike earlier designs, no attempt to convert Type3 to TTF at runtime
|
||||
- All conversions must be pre-built in library
|
||||
- Unknown Type3 fonts keep their Type3 definition
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
### Backend (Java)
|
||||
- `PdfJsonConversionService.java` - Main conversion logic
|
||||
- `Type3FontConversionService.java` - Signature calculation and matching
|
||||
- `Type3FontLibrary.java` - Library loading and lookup
|
||||
- `Type3GlyphExtractor.java` - Extract glyph data from Type3 fonts
|
||||
- `Type3FontSignatureCalculator.java` - Create font fingerprints
|
||||
- `PdfJsonFontType3Glyph.java` - Model for Type3 glyph data
|
||||
|
||||
### Frontend (TypeScript)
|
||||
- `pdfJsonEditorTypes.ts` - Type definitions for JSON structure
|
||||
- `pdfJsonEditorUtils.ts` - Font handling utilities
|
||||
|
||||
### Resources
|
||||
- `type3/library/index.json` - Font library metadata
|
||||
- `type3/library/fonts/` - Actual font files (TTF/OTF)
|
||||
- `settings.yml.template` - Configuration options
|
||||
|
||||
### Documentation
|
||||
- `pdf_text_edit_flow.md` - Overall text editing architecture
|
||||
- `type3_fallback_plan.md` - Original design and planning
|
||||
50
docs/pdf_text_edit_flow.md
Normal file
50
docs/pdf_text_edit_flow.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# PDF Text Edit Flow
|
||||
|
||||
This high-level diagram shows every major component involved when a user edits text inside a PDF via the JSON editor. It highlights where fonts (especially Type3) are captured, matched against the library, and re-applied during export.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% Upload & Extraction
|
||||
A([Upload PDF]) --> B[PdfJsonConversionService]
|
||||
B --> B1[Optional Ghostscript preflight]
|
||||
B1 --> B2[Iterate pages & resources]
|
||||
B2 --> B3[Extract text runs + fonts]
|
||||
|
||||
%% Font handling (serial tree)
|
||||
B3 --> C{Font subtype?}
|
||||
C -->|Type 0 / TrueType / CID| C1[Copy embedded program bytes]
|
||||
C -->|Type3| C2[Type3FontConversionService]
|
||||
C1 --> C4[Attach font payload + metadata]
|
||||
C2 --> C21{Library match?}
|
||||
C21 --|Yes|--> C22[Inject canonical TTF/OTF from library]
|
||||
C21 --|No|--> C23[Mark unsupported<br/>& keep Type3 glyphs]
|
||||
C2 --> C25[Record glyph charCodes + unicode mapping]
|
||||
C22 --> C25
|
||||
C23 --> C25
|
||||
|
||||
%% JSON output
|
||||
C4 --> D[Build PdfJsonDocument (pages, fonts, elements)]
|
||||
C25 --> D
|
||||
D --> E([Send JSON to UI])
|
||||
|
||||
%% Edit round-trip
|
||||
E --> F[User edits text/elements]
|
||||
F --> G[Patched JSON POSTed back]
|
||||
G --> H{Regeneration pipeline}
|
||||
H --> H1[Resolve fonts + candidates]
|
||||
H1 --> H11[Prefer library/embedded payloads]
|
||||
H1 --> H12[Fallback font service for missing glyphs]
|
||||
H --> H2{Can rewrite token stream?}
|
||||
H2 -->|Yes| H21[Rewrite existing operators]
|
||||
H2 -->|No| H22[Full page regeneration]
|
||||
H22 --> H23[Embed canonical fonts + Type3 glyph codes]
|
||||
H21 --> I[Apply annotations/metadata]
|
||||
H23 --> I
|
||||
I --> J([Download edited PDF])
|
||||
```
|
||||
|
||||
**Key points**
|
||||
- Type3 conversion happens entirely inside `Type3FontConversionService`. Matching entries pull canonical fonts from the library; when a signature is missing we simply keep the original Type3 glyph codes until a library entry is added.
|
||||
- Raw Type3 char codes are preserved in `PdfJsonTextElement.charCodes` so edits can fall back to the original glyph sequence when users do not change the text.
|
||||
- When the frontend submits changes, the backend preflights each text run, picks the proper font candidate (library > embedded > fallback), and rewrites the PDF with either token replacements or full page regeneration.
|
||||
- Glyph coverage metadata from the Type3 library now informs which fonts can legitimately render new characters, so added text keeps using the original Type3 face whenever its coverage includes those code points.
|
||||
13216
docs/type3/harvest_report.json
Normal file
13216
docs/type3/harvest_report.json
Normal file
File diff suppressed because it is too large
Load Diff
191
docs/type3/signature_inventory.md
Normal file
191
docs/type3/signature_inventory.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Type3 Signature Inventory
|
||||
|
||||
_Generated from `docs\type3\signatures`. Run `scripts/summarize_type3_signatures.py` after capturing new samples._
|
||||
|
||||
## Alias: `cmex10`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:bdff85a28d968fea7fbffcf4869b1a28bdd7eb8b73230a016b41bdcbe28de94b` | `01_Matplotlib.pdf` | 1 | 90 |
|
||||
|
||||
## Alias: `cmmi10`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:6c72170517812e39f970746f53a2ae08dafbbe7374c20bcb4d5a60adc49cb77b` | `01_Matplotlib.pdf` | 2 | 100, 120 |
|
||||
|
||||
## Alias: `cmr10`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:5b535a05c982fb8ff029dfbedd5e9d28c1c4379ebac259d207f65606a94e5b15` | `01_Matplotlib.pdf` | 3 | 48, 49, 53 |
|
||||
|
||||
## Alias: `cmsy10`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:1324cd8127143ef9023616b7911c570db3b1eb35758cdc9258ec16c0f4587775` | `01_Matplotlib.pdf` | 1 | 48 |
|
||||
| `sha256:2832e219b2db3bacf0d5a147d4b74ad5226fdf7562c395ef3fb12937633e037d` | `01_Matplotlib.pdf` | 1 | 8734 |
|
||||
|
||||
## Alias: `dejavusans`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e608894439ef7224c35b77f5d` | `01_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 53, 55 |
|
||||
| `sha256:994c963d70041eee141fd275fa22c525a71283de2b4a952814d02e0bbfa8caea` | `01_Matplotlib.pdf` | 4 | 46, 48, 49, 53 |
|
||||
| `sha256:93573cb1ab32b9cb09378298fb120de079f6a309908d2ee86f91392a6aba5c31` | `01_Matplotlib.pdf` | 30 | 32, 45, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
| `sha256:4febfad91e0141f9658506a0bf8fc2a449f0ea7d97b44e95fc9a970c77af4b0a` | `01_Matplotlib.pdf` | 7 | 48, 49, 50, 51, 52, 53, 54 |
|
||||
| `sha256:0386e5811612ba4b998d57cd3869d7fbc48092a79d436deda774af107a4af813` | `01_Matplotlib.pdf` | 9 | 46, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
| `sha256:b95fa2a272cbc950b81320790d04fcf19ebb24050fa2139ba6a474172cac596b` | `01_Matplotlib.pdf` | 20 | 32, 40, 41, 48, 50, 52, 54, 56, 83, 85 |
|
||||
| `sha256:d034d16ac69e3e1c5008e77c4c24dc3179308a2742601e89d5c8ab327e4040dd` | `01_Matplotlib.pdf` | 15 | 43, 48, 49, 50, 51, 56, 61, 97, 98, 105 |
|
||||
| `sha256:85e16e36ed0290c149647be7e468a7c46e7b66fd290131213040f7bad905aa44` | `01_Matplotlib.pdf` | 25 | 32, 45, 46, 48, 49, 50, 52, 53, 54, 56 |
|
||||
| `sha256:85e16e36ed0290c149647be7e468a7c46e7b66fd290131213040f7bad905aa44` | `01_Matplotlib.pdf` | 25 | 32, 45, 46, 48, 49, 50, 52, 53, 54, 56 |
|
||||
| `sha256:3654d4d9bcbbf6ad51628082203094069a17aad3a5e6f5c7972833566e42ab6b` | `01_Matplotlib.pdf` | 29 | 32, 46, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
| `sha256:d0c6cddc416d130701395246621a0f669fc292df4097a7a74395602faf4475df` | `01_Matplotlib.pdf` | 17 | 48, 49, 53, 97, 100, 101, 102, 103, 105, 108 |
|
||||
| `sha256:cadf43a2df81340368af44c76b499223931d78dcc76c70cf4b4a93d133e368af` | `01_Matplotlib.pdf` | 27 | 32, 46, 48, 49, 50, 52, 54, 56, 84, 97 |
|
||||
| `sha256:f1a874c4268b1bffffc99acabbe0a60aa662611b4bac0e688e4fc0ae3f2033bb` | `01_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 54, 56 |
|
||||
| `sha256:e3d87c113463c8642a4f22943064fd75c133ad31fe5efebf6de6abf211b74b5a` | `01_Matplotlib.pdf` | 7 | 48, 49, 50, 51, 52, 54, 56 |
|
||||
| `sha256:d47afb7581e98f588f0e70953e8692249aaa2ec3df36fbd90985f27b1ce1cf50` | `01_Matplotlib.pdf` | 12 | 46, 48, 49, 50, 51, 52, 53, 54, 55, 97 |
|
||||
| `sha256:e47b8f112a875361a43bcb6d9c6467e0296412d29e417e58a0e60c90b664d281` | `01_Matplotlib.pdf` | 9 | 46, 48, 50, 97, 99, 105, 108, 115, 118 |
|
||||
| `sha256:9c67df2ac5c3dcf957dfb0cd048fa450322a72b5a2dfb05f816c536b3b090607` | `01_Matplotlib.pdf` | 19 | 32, 39, 46, 48, 49, 50, 53, 58, 97, 99 |
|
||||
| `sha256:3ee773a0af6fdedb9853dca9f4d8b80a421a0024bdf06bea41f15d58e3b90c87` | `01_Matplotlib.pdf` | 13 | 46, 48, 49, 50, 52, 54, 56, 97, 99, 105 |
|
||||
| `sha256:4fa06c90399d80b41cb718163a5d78af2b203df6b6579246fb0b24d349b7a591` | `01_Matplotlib.pdf` | 15 | 46, 48, 49, 50, 52, 53, 54, 56, 97, 98 |
|
||||
| `sha256:e47b8f112a875361a43bcb6d9c6467e0296412d29e417e58a0e60c90b664d281` | `01_Matplotlib.pdf` | 9 | 46, 48, 50, 97, 99, 105, 108, 115, 118 |
|
||||
| `sha256:ac6756c76d6e43b771cc1e643dfc7891dfaaac05aa5e302190d0a662838ab031` | `01_Matplotlib.pdf` | 13 | 46, 48, 49, 50, 51, 53, 55, 97, 99, 105 |
|
||||
| `sha256:bf7b95498f7d00d228c5c155db62b6c1aa7e0215cca1690c9fdb0adcfd496b10` | `01_Matplotlib.pdf` | 14 | 46, 48, 49, 50, 65, 66, 67, 68, 97, 99 |
|
||||
| `sha256:39b8e5ec8e20a788cd45166baf0ab796397f152c9cd8dec1f882c635380cad92` | `01_Matplotlib.pdf` | 13 | 46, 48, 49, 50, 52, 53, 54, 97, 99, 105 |
|
||||
| `sha256:27b98489865df8df55f19e4505c093501f236465885ca3bf5b66b6f047a85bb2` | `01_Matplotlib.pdf` | 19 | 46, 48, 49, 50, 51, 52, 68, 70, 72, 76 |
|
||||
| `sha256:497ddd27e1f56ef6504c61613e3a159bab13314a4970a3be13b3a556648964da` | `01_Matplotlib.pdf` | 12 | 46, 48, 49, 50, 51, 97, 98, 99, 105, 108 |
|
||||
| `sha256:3b41f9e5f3a7ffa6f4cdffa2a46f02781ec1b2b0c99994707cfb139aa15a11e2` | `01_Matplotlib.pdf` | 5 | 32, 48, 49, 50, 53 |
|
||||
| `sha256:93723fe436a1aa654db56caf133f56280444b9dc0682af50b83787c3e49ee3ec` | `01_Matplotlib.pdf` | 16 | 32, 48, 49, 50, 52, 53, 54, 56, 58, 97 |
|
||||
| `sha256:a648cb0524465bcb3bf4a2f65e0761cfc5167b1871a7db9488bee11b56062727` | `01_Matplotlib.pdf` | 14 | 32, 46, 48, 49, 50, 52, 53, 54, 56, 58 |
|
||||
| `sha256:2f18ed7f982aeb954aaae388ba0c75e3c676717ca324156b42bb17f3f20ef403` | `01_Matplotlib.pdf` | 18 | 46, 48, 49, 50, 52, 54, 97, 99, 101, 102 |
|
||||
| `sha256:18ce863feb57f42f2b92ac85a8c55ef3eeaa15488c5d6cd8c724b085994c64fa` | `01_Matplotlib.pdf` | 12 | 46, 48, 49, 50, 51, 52, 97, 99, 105, 108 |
|
||||
| `sha256:a3eb7054e426aad7d1fac1f39ad6d3f886e34c04f780def5cf22b53cb3a45b46` | `01_Matplotlib.pdf` | 15 | 46, 48, 49, 50, 52, 53, 54, 55, 56, 97 |
|
||||
| `sha256:edd22119635bfb0f2bff750137c6c6400a7fae4ff80cc252d2e6f2ca88f599a7` | `01_Matplotlib.pdf` | 15 | 46, 48, 49, 50, 51, 52, 53, 54, 56, 97 |
|
||||
| `sha256:aae1797f3e3ff55d71b02590333aff86663d6bb4a5768bed7550e5987f40afe8` | `01_Matplotlib.pdf` | 14 | 46, 48, 49, 50, 52, 53, 54, 56, 97, 99 |
|
||||
| `sha256:0165552fad28860f2ea6079be7a87ea0833acde99309b3ef619c8f81707c46a3` | `01_Matplotlib.pdf` | 14 | 46, 48, 49, 50, 51, 52, 54, 97, 99, 105 |
|
||||
| `sha256:792a1c5aaa1743ab203a363a8f6cd07c3b043e33c72e97c4ea21f5862158e6c1` | `01_Matplotlib.pdf` | 17 | 43, 46, 48, 49, 50, 51, 52, 53, 54, 56 |
|
||||
| `sha256:f4bfd64f36bf33dea79800561a67f78d5ccdb436363574abf0892f58b376a2e6` | `01_Matplotlib.pdf` | 25 | 32, 46, 48, 49, 50, 51, 52, 53, 54, 85 |
|
||||
| `sha256:119da04d962622c8aa46d77f6bdfccb5d4a4ef7173775275b046efd59098e5d9` | `01_Matplotlib.pdf` | 17 | 46, 48, 49, 50, 53, 55, 97, 101, 103, 105 |
|
||||
| `sha256:003af1c45e3a5ab09544e226eba25e3a70abfe6e36dd48584474cc7a497685f6` | `01_Matplotlib.pdf` | 28 | 32, 40, 41, 46, 48, 49, 50, 52, 54, 56 |
|
||||
| `sha256:88b3471db1978cc83233f249453806a8369c766b089b424c86c2584196ed5dbf` | `01_Matplotlib.pdf` | 14 | 46, 48, 49, 50, 52, 54, 56, 100, 101, 102 |
|
||||
| `sha256:a15cc90b7fc110cef4f07fe8a692d572e1289a9ee29c95732294662fded4e042` | `01_Matplotlib.pdf` | 16 | 32, 46, 48, 49, 50, 51, 53, 79, 83, 97 |
|
||||
| `sha256:fb54c23aa081562ac114676ffe43032c9c0fb63af3e5b7b3441b88872d1f2e7a` | `01_Matplotlib.pdf` | 16 | 46, 48, 49, 50, 52, 53, 54, 56, 66, 72 |
|
||||
| `sha256:4b553d51d58f5891af071359fb016caf1c6137778da129a6b208dcc8cb0c4635` | `01_Matplotlib.pdf` | 9 | 46, 48, 49, 50, 51, 52, 53, 54, 56 |
|
||||
| `sha256:93573cb1ab32b9cb09378298fb120de079f6a309908d2ee86f91392a6aba5c31` | `01_Matplotlib.pdf` | 30 | 32, 45, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
| `sha256:4febfad91e0141f9658506a0bf8fc2a449f0ea7d97b44e95fc9a970c77af4b0a` | `01_Matplotlib.pdf` | 7 | 48, 49, 50, 51, 52, 53, 54 |
|
||||
| `sha256:0386e5811612ba4b998d57cd3869d7fbc48092a79d436deda774af107a4af813` | `01_Matplotlib.pdf` | 9 | 46, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
| `sha256:b95fa2a272cbc950b81320790d04fcf19ebb24050fa2139ba6a474172cac596b` | `01_Matplotlib.pdf` | 20 | 32, 40, 41, 48, 50, 52, 54, 56, 83, 85 |
|
||||
| `sha256:b318f65b9dc209eb6f004e3a6c20a772ebbca3d752adc10c66a6a8a479da2838` | `01_Matplotlib.pdf` | 20 | 32, 40, 41, 46, 48, 49, 50, 52, 53, 54 |
|
||||
| `sha256:64f725573c1f5d90196e94ed338a7af06baf274420414befeb9693c80acd0f77` | `01_Matplotlib.pdf` | 23 | 32, 46, 48, 49, 50, 51, 52, 53, 55, 57 |
|
||||
| `sha256:9a701e082ba5a779e2b20b8de0c7844b3f7838ba8cd4bd7ef366893761fb994d` | `01_Matplotlib.pdf` | 10 | 46, 48, 49, 50, 51, 52, 53, 54, 55, 56 |
|
||||
| `sha256:2f6f8d63ff6235f3b7cd6f5eba8076854892037afa2ea6962953b3e7cda3736e` | `01_Matplotlib.pdf` | 31 | 32, 40, 41, 44, 61, 65, 66, 67, 68, 70 |
|
||||
| `sha256:f17b5eb0ee996d1388c548f79fa50fa2d8c6076959eff189bb745d156d54547f` | `01_Matplotlib.pdf` | 26 | 32, 39, 65, 66, 67, 68, 73, 97, 98, 100 |
|
||||
| `sha256:f22c75548364bb25fc3efbe11f05c56e29f07c15c3046ddbc85a64e5cc5a97bd` | `01_Matplotlib.pdf` | 21 | 32, 48, 49, 50, 72, 80, 91, 93, 99, 100 |
|
||||
| `sha256:54a6c2e4bc290b48e21eece7f81cb6633c4b53a91f198fdaabfc73743b0e4499` | `01_Matplotlib.pdf` | 15 | 48, 49, 50, 51, 52, 53, 54, 97, 100, 101 |
|
||||
| `sha256:059af9dbaaab27c1d660ef00de6d4fd6e1687cfe2abca0a4c07265c2b2b450c6` | `01_Matplotlib.pdf` | 22 | 32, 46, 48, 49, 50, 51, 53, 55, 68, 77 |
|
||||
| `sha256:6651550d7b913850087244b7a70961989c2efc6d8c8d060d8663ff087b7723f6` | `01_Matplotlib.pdf` | 20 | 32, 46, 48, 49, 50, 52, 54, 56, 97, 100 |
|
||||
| `sha256:4d4ee6f04f57a40a589741df4747990ed485c192b0fc179a415aba822f352a8d` | `01_Matplotlib.pdf` | 26 | 32, 45, 48, 49, 50, 51, 52, 53, 56, 65 |
|
||||
| `sha256:4febfad91e0141f9658506a0bf8fc2a449f0ea7d97b44e95fc9a970c77af4b0a` | `01_Matplotlib.pdf` | 7 | 48, 49, 50, 51, 52, 53, 54 |
|
||||
| `sha256:e808a8ecba94bf0190ab7218bb0702698125ee2e456e82e00da709e8188e2bf8` | `01_Matplotlib.pdf` | 28 | 32, 46, 48, 49, 50, 53, 54, 70, 83, 84 |
|
||||
| `sha256:b5064b202eb1dae41545eddf674ee23bd82176e76aac8eb749540c2689f2e3ec` | `01_Matplotlib.pdf` | 33 | 32, 33, 37, 48, 49, 50, 52, 53, 54, 68 |
|
||||
| `sha256:f8f14410ec170248916e19f9d09120cfd786c47906b7c3735781d24e944b094e` | `01_Matplotlib.pdf` | 11 | 32, 83, 84, 101, 109, 110, 111, 115, 116, 119 |
|
||||
| `sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e608894439ef7224c35b77f5d` | `01_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 53, 55 |
|
||||
| `sha256:994c963d70041eee141fd275fa22c525a71283de2b4a952814d02e0bbfa8caea` | `01_Matplotlib.pdf` | 4 | 46, 48, 49, 53 |
|
||||
| `sha256:c43134bebeaf8328ac299ba978d7e663e2dc4fe99463b9d7f72f72f77936204e` | `01_Matplotlib.pdf` | 9 | 48, 49, 50, 51, 52, 53, 54, 55, 56 |
|
||||
| `sha256:4f763d5e2cd0bdcd4650936ac505bd0e011899712ffe80ffa4b4d43f42941327` | `01_Matplotlib.pdf` | 10 | 46, 48, 49, 50, 52, 54, 56, 98, 103, 114 |
|
||||
| `sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e608894439ef7224c35b77f5d` | `01_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 53, 55 |
|
||||
| `sha256:cb72de0c6105b9802d360c47a292a1f7bc344939a6801b879ea09dae4e45e863` | `01_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 51, 52, 53 |
|
||||
| `sha256:2add5b5ad6e536f3614b75e246b49a006edbbecdd309d24bd42c874a3ae3c8ed` | `01_Matplotlib.pdf` | 21 | 45, 48, 49, 50, 51, 52, 97, 99, 100, 101 |
|
||||
| `sha256:85e16e36ed0290c149647be7e468a7c46e7b66fd290131213040f7bad905aa44` | `02_Matplotlib.pdf` | 25 | 32, 45, 46, 48, 49, 50, 52, 53, 54, 56 |
|
||||
| `sha256:85e16e36ed0290c149647be7e468a7c46e7b66fd290131213040f7bad905aa44` | `02_Matplotlib.pdf` | 25 | 32, 45, 46, 48, 49, 50, 52, 53, 54, 56 |
|
||||
| `sha256:059af9dbaaab27c1d660ef00de6d4fd6e1687cfe2abca0a4c07265c2b2b450c6` | `02_Matplotlib.pdf` | 22 | 32, 46, 48, 49, 50, 51, 53, 55, 68, 77 |
|
||||
| `sha256:9a701e082ba5a779e2b20b8de0c7844b3f7838ba8cd4bd7ef366893761fb994d` | `02_Matplotlib.pdf` | 10 | 46, 48, 49, 50, 51, 52, 53, 54, 55, 56 |
|
||||
| `sha256:2f6f8d63ff6235f3b7cd6f5eba8076854892037afa2ea6962953b3e7cda3736e` | `02_Matplotlib.pdf` | 31 | 32, 40, 41, 44, 61, 65, 66, 67, 68, 70 |
|
||||
| `sha256:f17b5eb0ee996d1388c548f79fa50fa2d8c6076959eff189bb745d156d54547f` | `02_Matplotlib.pdf` | 26 | 32, 39, 65, 66, 67, 68, 73, 97, 98, 100 |
|
||||
| `sha256:6651550d7b913850087244b7a70961989c2efc6d8c8d060d8663ff087b7723f6` | `02_Matplotlib.pdf` | 20 | 32, 46, 48, 49, 50, 52, 54, 56, 97, 100 |
|
||||
| `sha256:4febfad91e0141f9658506a0bf8fc2a449f0ea7d97b44e95fc9a970c77af4b0a` | `02_Matplotlib.pdf` | 7 | 48, 49, 50, 51, 52, 53, 54 |
|
||||
| `sha256:e808a8ecba94bf0190ab7218bb0702698125ee2e456e82e00da709e8188e2bf8` | `02_Matplotlib.pdf` | 28 | 32, 46, 48, 49, 50, 53, 54, 70, 83, 84 |
|
||||
| `sha256:b5064b202eb1dae41545eddf674ee23bd82176e76aac8eb749540c2689f2e3ec` | `02_Matplotlib.pdf` | 33 | 32, 33, 37, 48, 49, 50, 52, 53, 54, 68 |
|
||||
| `sha256:f8f14410ec170248916e19f9d09120cfd786c47906b7c3735781d24e944b094e` | `02_Matplotlib.pdf` | 11 | 32, 83, 84, 101, 109, 110, 111, 115, 116, 119 |
|
||||
| `sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e608894439ef7224c35b77f5d` | `02_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 53, 55 |
|
||||
| `sha256:994c963d70041eee141fd275fa22c525a71283de2b4a952814d02e0bbfa8caea` | `02_Matplotlib.pdf` | 4 | 46, 48, 49, 53 |
|
||||
| `sha256:c43134bebeaf8328ac299ba978d7e663e2dc4fe99463b9d7f72f72f77936204e` | `02_Matplotlib.pdf` | 9 | 48, 49, 50, 51, 52, 53, 54, 55, 56 |
|
||||
| `sha256:4f763d5e2cd0bdcd4650936ac505bd0e011899712ffe80ffa4b4d43f42941327` | `02_Matplotlib.pdf` | 10 | 46, 48, 49, 50, 52, 54, 56, 98, 103, 114 |
|
||||
| `sha256:2be58b6ef1e29a83b8634d70b9e32c37a15dea2e608894439ef7224c35b77f5d` | `02_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 52, 53, 55 |
|
||||
| `sha256:cb72de0c6105b9802d360c47a292a1f7bc344939a6801b879ea09dae4e45e863` | `02_Matplotlib.pdf` | 7 | 46, 48, 49, 50, 51, 52, 53 |
|
||||
| `sha256:31d0e67bc63a816302c9ff6ad9c19e17603aef1a4c3677b81b1d9084caa86e03` | `03_handout-beginner.pdf` | 6 | 46, 48, 49, 50, 51, 53 |
|
||||
| `sha256:4b509d2ae2cfab89783a73df2c66f0fd50949f97696079cb58f1e58b81daaa07` | `03_handout-beginner.pdf` | 4 | 84, 101, 105, 109 |
|
||||
| `sha256:831f7012db360331ffb5a5de6a6d6e03ffaad29f48d81cabe9fc613b25aad818` | `04_handout-intermediate.pdf` | 43 | 32, 40, 41, 46, 48, 49, 50, 51, 52, 53 |
|
||||
| `sha256:bf790625423c5ebdf94760eb796c847af885b930d3a30861509b07f1c77c3f60` | `04_handout-intermediate.pdf` | 9 | 46, 48, 49, 50, 51, 52, 53, 54, 56 |
|
||||
| `sha256:f7c3be2199c397a4c702dd434ac63fc9e046d749eff8cede4513fbc2774751b4` | `04_handout-intermediate.pdf` | 5 | 48, 49, 50, 51, 53 |
|
||||
| `sha256:8f7bf7a6382e8a762c5a84f19f84f0675f61eb1b34bd42562c0b3ac6712e29ef` | `04_handout-intermediate.pdf` | 2 | 48, 49 |
|
||||
| `sha256:dfaf8075e13be0e51f72485f9d825cea9ad077eb2dd9d63b9922add67d7d2761` | `04_handout-intermediate.pdf` | 12 | 32, 48, 49, 50, 51, 53, 80, 100, 101, 105 |
|
||||
| `sha256:853422e67ac88fe7ae28d5c459dc9f5a84f24e7840eeb2d82a00719032119326` | `04_handout-intermediate.pdf` | 10 | 32, 67, 83, 97, 100, 101, 105, 110, 111, 115 |
|
||||
| `sha256:b42182c55ec4bd53ab0698bee5f92945921dbccb534fdb5c6b41f1782e1fe88e` | `04_handout-intermediate.pdf` | 7 | 32, 48, 49, 50, 51, 53, 65 |
|
||||
| `sha256:75466035ac34f2523215e599452e32d796d7d02bc7122ed3d02fe91ebe064c25` | `04_handout-intermediate.pdf` | 6 | 48, 49, 50, 52, 54, 56 |
|
||||
| `sha256:75466035ac34f2523215e599452e32d796d7d02bc7122ed3d02fe91ebe064c25` | `04_handout-intermediate.pdf` | 6 | 48, 49, 50, 52, 54, 56 |
|
||||
|
||||
## Alias: `dejavusans-bold`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:dc03917f2edd92a7a68a46ad36f65a908e4feb85e61cb37e9026205f3986574a` | `01_Matplotlib.pdf` | 7 | 65, 87, 100, 101, 103, 105, 116 |
|
||||
| `sha256:dc03917f2edd92a7a68a46ad36f65a908e4feb85e61cb37e9026205f3986574a` | `02_Matplotlib.pdf` | 7 | 65, 87, 100, 101, 103, 105, 116 |
|
||||
| `sha256:c845063bef18f173afbfcb90fbf6773f43648c5f0666ecfa0132afe4e164068d` | `03_handout-beginner.pdf` | 9 | 32, 65, 83, 97, 101, 105, 110, 118, 119 |
|
||||
|
||||
## Alias: `dejavusans-oblique`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:81cd2d4d9353ee02c7ed80c2892658072b2a8bbd9ed1832b474129dfbe35d5d8` | `01_Matplotlib.pdf` | 13 | 70, 71, 85, 87, 100, 101, 103, 109, 112, 114 |
|
||||
| `sha256:08864aa8e8d17cead6059d5b4f1b1eea2053fa0ea3ca64e885d6eaacb78bccaf` | `01_Matplotlib.pdf` | 2 | 100, 120 |
|
||||
|
||||
## Alias: `dejavusansdisplay`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:ae77c4eb2c49f72c616272f3d7ac624ddb0b4db1c77acbe6b9d13531f68e1d5d` | `01_Matplotlib.pdf` | 0 | |
|
||||
| `sha256:ae77c4eb2c49f72c616272f3d7ac624ddb0b4db1c77acbe6b9d13531f68e1d5d` | `01_Matplotlib.pdf` | 0 | |
|
||||
|
||||
## Alias: `dejavusansmono`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:88758adf0b41a81204ed3ad63463f5d15c7c2f80e8942cee501d06fa7274dc4e` | `01_Matplotlib.pdf` | 8 | 97, 98, 105, 108, 109, 111, 112, 116 |
|
||||
| `sha256:74e60bcb2d7975b0c7b372aca9fc25f55c9018005425a741830e7c4370b8d593` | `01_Matplotlib.pdf` | 24 | 35, 39, 48, 49, 50, 51, 52, 53, 54, 55 |
|
||||
|
||||
## Alias: `f36`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:72c7041f938222b87ce2d9295547f8c19edf250af538160b69be35a968d76ea7` | `08_matplotlib.pdf` | 1 | 136 |
|
||||
|
||||
## Alias: `f59`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:d5942c0913ef64ae862556a746b28dac1a621caa5e05973e16881c8e8e15e329` | `08_matplotlib.pdf` | 1 | 42 |
|
||||
|
||||
## Alias: `sourcecodepro-regular`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:96ba693001b2ab224ad5b5a7464cecd4d33e68f30fb23f78a8473dbb031ce246` | `04_handout-intermediate.pdf` | 11 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 |
|
||||
| `sha256:72fca14e9e44fc41b0cdb1c6a088f0b07f882f9f04c51a0145f43cf8b285c5b6` | `04_handout-intermediate.pdf` | 11 | 46, 48, 49, 50, 51, 52, 53, 54, 55, 56 |
|
||||
|
||||
## Alias: `stixsizethreesym-regular`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:33d0ab9d9d72c1aed1edfc9b815dd6a2d618cbbe9084309c7f2de0f3df3073d7` | `01_Matplotlib.pdf` | 2 | 91, 93 |
|
||||
|
||||
## Alias: `unknown`
|
||||
|
||||
| Signature | Samples | Glyph Count | Coverage (first 10) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha256:23e4b174e951cd6135e229fb397db4ff518021cf14d5f817031b9f754841e511` | `07_matplotlib.pdf` | 11 | 0, 46, 97, 98, 105, 108, 109, 111, 112, 116 |
|
||||
| `sha256:f6112d6a35d5fdf5d6431b3156b713020953154042814ad12d2b81731c97250b` | `07_matplotlib.pdf` | 13 | 0, 32, 37, 97, 98, 101, 105, 108, 109, 110 |
|
||||
| `sha256:7e05c074b630c0f3e1fc23537c22bf4b6191c783601e534cd156f71c1827702c` | `07_matplotlib.pdf` | 42 | 0, 32, 34, 37, 39, 40, 41, 42, 44, 46 |
|
||||
| `sha256:9958bc1f309f6bafb55834e271bb0b337704fcac51f6d989abe9553fcffa103d` | `07_matplotlib.pdf` | 15 | 0, 46, 97, 98, 105, 108, 109, 110, 111, 112 |
|
||||
150
docs/type3_fallback_plan.md
Normal file
150
docs/type3_fallback_plan.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Type3 Font Library & Matching Plan
|
||||
|
||||
This file documents where we are with Type3 font handling, what tooling already exists, and what remains to be done so future work (or another Codex session) can pick it up quickly.
|
||||
|
||||
## Goal
|
||||
Ensure Type3 fonts keep their appearance when users edit/export PDFs. That means:
|
||||
1. Identifying common Type3 fonts we encounter (Matplotlib, LaTeX, etc.).
|
||||
2. Capturing their glyph outlines once, converting them to reusable TTF/OTF binaries.
|
||||
3. At runtime, matching Type3 fonts in incoming PDFs against that library (by signature) so we can embed the canonical TTF instead of falling back to generic fonts.
|
||||
4. Using the captured char-code sequences so regeneration and editing preserves glyphs even when original fonts had no ToUnicode map.
|
||||
|
||||
## Current State
|
||||
- **Extraction**: `PdfJsonTextElement` now stores raw Type3 char codes; `encodeTextWithFont` can use them so token-level rewrites keep original glyphs.
|
||||
- **Regeneration**: Page regeneration now uses those char codes when writing new content streams, so existing text should remain visible even when tokens must be rebuilt.
|
||||
- **Scripts**: `scripts/index_type3_catalogue.py` scans PDFs in `app/core/src/main/resources/type3/samples` with `pdffonts` and writes `catalogue.json` (basic list of Type3 fonts encountered). This is only the first step; we still need per-font signatures and converted binaries.
|
||||
- **Samples**: There are sample PDFs under `app/core/src/main/resources/type3/samples/` (Matplotlib slides, etc.) that we can mine for common Type3 fonts.
|
||||
- **Library matching**: `Type3FontLibrary` loads `type3/library/index.json`, and `Type3LibraryStrategy` injects the prebuilt TTF/OTF payloads straight into `PdfJsonFont` conversion candidates. At runtime this is now the *only* conversion path; if the library does not recognise a signature we fall back to the captured Type3 glyph codes instead of trying to synthesize a font on the fly.
|
||||
- **Offline conversion helpers**: `scripts/type3_to_cff.py` is still available for developers who need to turn a Type3-only PDF into a reusable TTF/OTF, but it is no longer wired into the server lifecycle. Everything shipped to users must be backed by the curated library.
|
||||
- **Signature CLI**: `Type3SignatureTool` (`./gradlew :proprietary:type3SignatureTool --args="--pdf sample.pdf --output meta.json --pretty"`) dumps every Type3 font in a PDF along with its signature + glyph coverage. Use this to extend `index.json` without touching the backend.
|
||||
- **Signature inventory**: `docs/type3/signatures/` stores the captured dumps, and `scripts/summarize_type3_signatures.py` keeps `docs/type3/signature_inventory.md` up to date so we know which aliases still need binaries.
|
||||
|
||||
## Remaining Work
|
||||
1. **Signature capture tooling**
|
||||
- ✅ `Type3SignatureTool` (`./gradlew :proprietary:type3SignatureTool`) dumps signature + glyph coverage JSON; keep them under `docs/type3/signatures`.
|
||||
- ✅ `scripts/summarize_type3_signatures.py` produces `docs/type3/signature_inventory.md` to highlight remaining gaps.
|
||||
- ✅ `scripts/harvest_type3_fonts.py --input <dir>` bulk-processes entire PDF folders, reusing cached signature JSON files and writing `docs/type3/harvest_report.json` so you can keep adding new samples over time.
|
||||
- ✅ `scripts/download_pdf_samples.py` downloads large batches of PDF URLs into a staging folder that can immediately be fed to the harvester.
|
||||
- ⏱️ Extend `scripts/index_type3_catalogue.py` to read those dumps so the catalogue and library stay in sync.
|
||||
|
||||
2. **Library coverage**
|
||||
- ✅ Added CM (cmr10/cmmi10/cmex10/cmsy10), STIX Size Three symbols, and SourceCodePro (SauceCode) using upstream TTF/OTF payloads.
|
||||
- 🔜 Add Matplotlib-only subsets (F36/F59). For proprietary Type3 shapes, use the offline FontTools helper (`scripts/type3_to_cff.py`) to generate TTF/OTF payloads, drop them under `type3/library/fonts/<family>/`, and reference them from `index.json`.
|
||||
- Each entry in `type3/library/index.json` should contain `{id, aliases, signatures, glyphCoverage, program/web/pdf payloads, source PDF}`.
|
||||
|
||||
3. **Glyph coverage metadata**
|
||||
- ✅ When adding a library entry, copy the `glyphCoverage` array from the signature JSON so runtime preflight knows exactly which code points exist. The backend now consults this data while building new text runs so characters stay on the original Type3 font whenever it supports them.
|
||||
|
||||
4. **Automation**
|
||||
- ✅ `scripts/update_type3_library.py` ingests the captured signature JSON files, merges their signatures/aliases/glyph coverage into `app/core/src/main/resources/type3/library/index.json`, and reports any fonts that still lack entries. Run it with `--apply` after harvesting new samples.
|
||||
|
||||
5. **Validation**
|
||||
- 🔁 After each new library entry, run a JSON→PDF roundtrip on the source PDF to confirm edited text sticks with the canonical font (FontTools stays disabled unless the font is missing).
|
||||
|
||||
## Tooling/Dependencies
|
||||
- Requires `pdffonts` (poppler-utils) for the current indexing script.
|
||||
- Optional: `scripts/type3_to_cff.py` (fontTools) when you need to manufacture a TTF/OTF for an otherwise Type3-only font before adding it to the library.
|
||||
- Backend relies on PDFBox 3.x.
|
||||
|
||||
## Library Onboarding Workflow
|
||||
Follow this loop whenever you encounter a new Type3 face that is missing from the library:
|
||||
|
||||
1. **Capture signatures**
|
||||
Run `./gradlew :proprietary:type3SignatureTool --args="--pdf path/to/sample.pdf --output docs/type3/signatures/<name>.json --pretty"` to dump the font’s signature, glyph coverage, and aliases. Commit the JSON under `docs/type3/signatures/`.
|
||||
|
||||
2. **Harvest more samples (optional)**
|
||||
Use `scripts/harvest_type3_fonts.py --input <folder>` to bulk-run the signature tool across a directory of PDFs. This keeps `docs/type3/signature_inventory.md` fresh so you can see how often each alias appears.
|
||||
|
||||
3. **Collect a canonical TTF/OTF**
|
||||
- If the font is really just a subset of a known family (DejaVu, Computer Modern, STIX, etc.), copy the upstream TTF/OTF into `app/core/src/main/resources/type3/library/fonts/<family>/`.
|
||||
- If no canonical binary exists, feed the sample PDF through `scripts/type3_to_cff.py --input glyphs.json --ttf-output <path>` to synthesize one offline. Review the glyphs visually before committing.
|
||||
|
||||
4. **Update the library index**
|
||||
Reference the binary from `app/core/src/main/resources/type3/library/index.json` (use the `resource` field so the build packs the raw TTF/OTF). Add the captured signatures, aliases, glyph coverage, and the PDF you mined as `source`.
|
||||
|
||||
5. **Apply bulk edits automatically**
|
||||
After dropping new signature dumps, run `scripts/update_type3_library.py --apply` to merge any missing signatures/aliases/coverage entries into `index.json`. The script prints a list of fonts that still lack binaries so you know what to tackle next.
|
||||
|
||||
6. **Verify the round-trip**
|
||||
Convert the sample PDF to JSON through the app, edit text to introduce new characters, and export it back to PDF. The logs should show `[TYPE3] Strategy type3-library finished with status SUCCESS`, and the output should keep the original styling even for the new glyphs.
|
||||
|
||||
Because the server no longer attempts runtime synthesis, once a font lands in the library it will stay stable across every deployment. Missing fonts simply fall back to their Type3 glyph codes until you add them to the index, so there is always a deterministic path forward.
|
||||
|
||||
## How to Use the Existing Script
|
||||
```
|
||||
# From repo root
|
||||
scripts/index_type3_catalogue.py \
|
||||
--samples app/core/src/main/resources/type3/samples \
|
||||
--output app/core/src/main/resources/type3/catalogue.json
|
||||
```
|
||||
Output is a simple JSON array with `source`, `fontName`, and `encoding`. This needs to be extended with signatures and references to the converted TTFs once that tooling is in place.
|
||||
|
||||
## Expected Outcomes
|
||||
- A deduplicated library of the most common Type3 fonts we encounter, each with a stable signature and prebuilt TTF/OTF.
|
||||
- Backend automatically matches a Type3 font to its library entry and embeds the canonical TTF during edit/export.
|
||||
- Fallback font usage drops dramatically; edited PDFs retain the original look with Type3Synth fonts only used when genuinely necessary.
|
||||
- Additional metrics (e.g., glyph coverage) stored in the catalogue so we can diagnose gaps quickly.
|
||||
|
||||
## Next Steps Checklist
|
||||
1. Capture signatures for every sample font and add them to `type3/library/index.json`.
|
||||
2. Extend catalogue JSON to include signatures + metadata.
|
||||
3. Batch-convert the remaining samples into the Type3 library (TTF/OTF files under `resources/type3/library/`).
|
||||
4. Provide doc or script for adding new fonts to the library.
|
||||
5. Run regression tests on sample PDFs to ensure original text remains visible and new text matches the Type3 font whenever possible.
|
||||
|
||||
## Library Layout Cheat Sheet
|
||||
- **Index**: `app/core/src/main/resources/type3/library/index.json`.
|
||||
- **Font payloads**: drop TTF/OTF data under `type3/library/fonts/<family>/<file>.ttf`.
|
||||
- **Entry schema**:
|
||||
```json
|
||||
{
|
||||
"id": "unique-id",
|
||||
"label": "Human readable name",
|
||||
"signatures": ["sha256:..."],
|
||||
"aliases": ["SubsetPrefix+RealName"],
|
||||
"program": {"resource": "type3/library/fonts/family/font.otf", "format": "otf"},
|
||||
"webProgram": {"resource": "...", "format": "ttf"},
|
||||
"pdfProgram": {"resource": "...", "format": "ttf"},
|
||||
"glyphCoverage": [32,65,66],
|
||||
"source": "Where the sample came from"
|
||||
}
|
||||
```
|
||||
- **Runtime flow**:
|
||||
1. `Type3FontConversionService` builds a `Type3ConversionRequest`.
|
||||
2. `Type3LibraryStrategy` hashes the font via `Type3FontSignatureCalculator`.
|
||||
3. If the signature/alias exists in the index, it injects the canonical payload as a `PdfJsonFontConversionCandidate`.
|
||||
4. `PdfJsonConversionService` prefers conversion candidates over embedded Type3 programs when reloading fonts, so new text uses the canonical TTF automatically.
|
||||
|
||||
### Signature Capture Tool
|
||||
```
|
||||
# Dump all Type3 fonts in a PDF, their signatures, and glyph coverage
|
||||
./gradlew :proprietary:type3SignatureTool \
|
||||
--args="--pdf app/core/src/main/resources/type3/samples/01_Matplotlib.pdf --output tmp/signatures.json --pretty"
|
||||
```
|
||||
Use the resulting JSON to fill `signatures`, `aliases`, and `glyphCoverage` in `type3/library/index.json`. Once an entry exists, runtime conversion will reuse that payload and skip the costly FontTools synthesis.
|
||||
|
||||
---
|
||||
Feel free to expand this plan or add notes as the work progresses.
|
||||
|
||||
---
|
||||
|
||||
## Practical Workflow (from PDF ingestion to runtime use)
|
||||
|
||||
| Stage | Tool / Command | Output |
|
||||
| --- | --- | --- |
|
||||
| 1. Collect PDFs | `python scripts/download_pdf_collection.py --output scripts/pdf-collection` (or drop your own PDFs anywhere) | Raw PDFs ready for harvesting |
|
||||
| 2. Harvest signatures | `python scripts/harvest_type3_fonts.py --input scripts/pdf-collection --pretty` | Per-PDF dumps in `docs/type3/signatures/…` + global summary `docs/type3/harvest_report.json` |
|
||||
| 3. Summarize backlog | `python scripts/summarize_type3_signatures.py` | `docs/type3/signature_inventory.md` (human checklist of aliases/signatures) |
|
||||
| 4. Convert fonts | Either copy the upstream TTF/OTF for the font (DejaVu, CM, STIX, etc.) or run `scripts/type3_to_cff.py` against the harvested glyph JSON to synthesize one offline; store the result under `app/core/src/main/resources/type3/library/fonts/<family>/`. | Canonical font binaries |
|
||||
| 5. Register entry | Edit `app/core/src/main/resources/type3/library/index.json` (add `id`, `aliases`, `signatures`, `glyphCoverage`, and point `program/web/pdf` to the binaries). | Runtime-ready index |
|
||||
| 6. Verify in app | Run a PDF→JSON→PDF roundtrip on a sample containing the font; check logs for `[TYPE3] Strategy type3-library finished with status SUCCESS`. | Confidence that edits use the canonical TTF |
|
||||
|
||||
### Expected artifacts in the repo
|
||||
- `scripts/pdf-collection/` — downloaded PDFs (input to the pipeline).
|
||||
- `docs/type3/signatures/<...>.json` — raw signature dumps (one per PDF).
|
||||
- `docs/type3/harvest_report.json` — deduplicated list of every signature encountered to date.
|
||||
- `docs/type3/signature_inventory.md` — Markdown table summarizing signatures/aliases for triage.
|
||||
- `app/core/src/main/resources/type3/library/fonts/<family>/<font>.ttf` — curated binaries.
|
||||
- `app/core/src/main/resources/type3/library/index.json` — mapping used at runtime.
|
||||
|
||||
Once an entry exists in `index.json`, the backend automatically attaches that TTF/OTF during PDF→JSON, caches a normalized PDFont, and uses it for JSON→PDF regeneration. This eliminates the `PDType3Font.encode` limitation and keeps edited text visually identical to the original Type3 output.
|
||||
Reference in New Issue
Block a user