Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add appendix B which contains an example of the extension algorithm execution. #237

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ encouraged to pre-fetch patch files that will be applied in later iterations by
to be applied. For example: in a case where there are only "No Invalidation" intersecting patches the client could safely load all
intersecting patches in parallel, since no patch application will invalidate any of the other intersecting patches.

<div class="example">
An example of the execution of the extension algorithm can be found in [[#extension-example]].
</div>

<dfn abstract-op>Check entry intersection</dfn>

The inputs to this algorithm are:
Expand Down Expand Up @@ -2705,3 +2709,234 @@ to be used by default in most shaper implementations. This list was assembled fr
<!-- Edit feature-registry.csv to update this table. -->
path: feature-registry.html
</pre>


<h2 id="extension-example">
Appendix B: Extension Algorithm Example Execution</h2>

This appendix provides an example of how a typical IFT font would be processed by the [[#extend-font-subset]] algorithm. In this example the IFT font
contains a mix of both [[#table-keyed]] and [[#glyph-keyed]] patches.

<b>Initial font</b>: contains the following IFT and IFTX patch mappings. Note: when features and design space sets are unspecified in a subset definition
they are defaulted to an empty set.

<table>
<tr><th>Table = "IFT "</th><th colspan="2">Compatibility ID = 0x0000_0000_0000_0001</th></tr>
<tr><th>Subset Definition(s)</th><th>URI</th><th>Format Number</th>
<tr>
<td>
```
code points: { 'a', 'b', ..., 'z' }
```
</td>
<td>//foo.bar/01.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>
<tr>
<td>
```
code points: { 'A', 'B', ..., 'Z' }
```
</td>
<td>//foo.bar/02.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>
<tr>
<td>
```
code points: { '0', '1', ..., '9' }
```
</td>
<td>//foo.bar/03.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>

<tr>
<td>
```
code points: { 'a', 'b', ..., 'z',
'A', 'B', ..., 'Z' }
```
</td>
<td>//foo.bar/04.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>
<tr>
<td>
```
code points: { 'a', 'b', ..., 'z',
'A', 'B', ..., 'Z',
'0', '1', ..., '9' }
```
</td>
<td>//foo.bar/05.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>
</table>
<br/>
<table>
<tr><th>Table = "IFTX"</th><th colspan="2">Compatibility ID = 0x0000_0000_0000_0002</th></tr>
<tr><th>Subset Definition(s)</th><th>URI</th><th>Format Number</th>
<tr>
<td>
```
code points: { 'a', 'b', ..., 'm' }
```
</td>
<td>//foo.bar/01.gk</td>
<td>
3, Glyph Keyed
</td>
</tr>
<tr>
<td>
```
code points: { 'n', 'o', ..., 'z' }
```
</td>
<td>//foo.bar/02.gk</td>
<td>
3, Glyph Keyed
</td>
</tr>
<tr>
<td>
```
code points: { 'A', 'B', ..., 'M' }
```
</td>
<td>//foo.bar/03.gk</td>
<td>
3, Glyph Keyed
</td>
</tr>
<tr>
<td>
```
code points: { 'N', 'O', ..., 'Z' }
```
</td>
<td>//foo.bar/04.gk</td>
<td>
3, Glyph Keyed
</td>
</tr>
<tr>
<td>
```
code points: { '0', '1', ..., '9' }
```
</td>
<td>//foo.bar/05.gk</td>
<td>
3, Glyph Keyed
</td>
</tr>
</table>

<b>Optimized Extension Example</b>

Note: this example execution is described as it would be implemented in an optimized client which aims to reduce the number of round trips needing to be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one aspect of this description needs updating in light of #238 (as expected) and the second isn't accurate relative to the example. This is will now be following the selection criteria rather than "making choices". And I don't think it's doing any speculative loading -- it's choosing 3 as it should. Or maybe that's what was meant.

I think going forward we can talk about the algorithm now that it's nailed down, and occasionally talk about speculative loading. For the latter I want to get away from the term "preload" because we're using that for adding patches to a file on the server side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the later step that talks about invalidating patch selection to reflect the changes in #238. Also as suggested changed preload to speculative.

For this particular note the speculative loading I'm talking about is for the glyph keyed patches (so not related to invalidating patch selection), I added some text to clarify that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this particular note the speculative loading I'm talking about is for the glyph keyed patches (so not related to invalidating patch selection), I added some text to clarify that.

The set requires 'f' and 'P' and we load 01.gk and 04.gk, so I don't see any speculative loading for those.

Copy link
Contributor Author

@garretrieger garretrieger Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are speculative in the sense that we have to get and apply the table keyed patch first and we don't know for sure exactly what it will do. Although unlikely it could modify the entry list for the glyph keyed patches and remove 01.gk and 04.gk entries making the loads for those two unnecessary. Maybe speculative isn't quite the right word here, how about preemptive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, hmm. From the text further down:

The table keyed patch //foo.bar/04.tk is partially invalidating which means the two glyph keyed patches will be remain valid after the application of //foo.bar/04.tk. Thus the client can expect that the two glyph keyed patches will be required in future iterations and preemptively begin the loads for those two patches.

What's ambiguous about the situation such that either "speculative" or "preemptive" really applies? If 04.tk could make the other two patches redundant, it would be one thing, but it can't.

I suppose loading patches in parallel can be seen as an "optimization" but in practice it will kill performance not to, so I don't think we should present it as "optional" except in the loosest sense.

Why is there any linkage between the two patch groups in the algorithm in the first place? If 01.gk arrives before 04.tk, can't you (in terms of correctness) apply it first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the invalidation modes guarantees that the glyph keyed patches in this case are always safe to apply. However, the invalidation rules don't prevent a table keyed patch from rewriting the glyph keyed map to point to a different set of URIs that still use the same compatibility base (eg. how we handle variable axis extension in the prototype). So there's no harm in applying the glyph keyed patches early, but it's possible (though unlikely) they may become unnecessary if they are superseded by new patches. Definitely agree that saving the round trip is always worth it even in the odd case where you grab patches you don't end up needing. I'll reword this a bit to tone down the "speculative" nature of this behaviour and just present it as what a good implementation should do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, updated the text. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the invalidation rules don't prevent a table keyed patch from rewriting the glyph keyed map to point to a different set of URIs that still use the same compatibility base (eg. how we handle variable axis extension in the prototype).

I thought this was the purpose for partial vs full invalidation patches. Full invalidation patches invalidate the patches in both IFT tables (or something approximating that) while partial invalidation patches only invalidate those in their own table. The example explicitly says the patches are from two separate tables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalidation is currently defined in terms of the compatibility IDs, that is a patch which invalidates a mapping will change the compatibility ID on that mapping which means any existing patches can no longer be applied. So a partial invalidation patch only promises not to change the compatibility ID of the other mapping table, but nothing in the text prohibits a partial invalidation patch from modifying the contents of the mapping (eg. swapping in a new set of patch uris so long as they all use the same compat IDs).

That said, I'm not sure this is a use case we actually need to support, so maybe we should update the text of the invalidation section to also say the patch also won't modify the contents of the mapping.

made to extend the font for a target subset definition. This optimized execution is fully compliant with the extension algorithm but
loads some URIs (the glyph keyed URIs) earlier than specified in the extension algorithm. This is allowed because these patches
will not be invalidated by the preceding table keyed patch.

Inputs:
* Initial font with the IFT and IFTX tables as defined above.
* Target subset definition: code points = {'f', 'P'}

Iteration 1:

* Steps 1 through 6 - applying the [$Check entry intersection$] check to all entries in the initial font, the following patch entries intersect:
* //foo.bar/01.tk
* //foo.bar/02.tk
* //foo.bar/04.tk
* //foo.bar/05.tk
* //foo.bar/01.gk
* //foo.bar/04.gk

* Step 7 - one entry from that set must be picked. There a no full invalidation entries, so one partial invalidation entry (//foo.bar/*.tk) must be
selected. Following [[#invalidating-patch-selection]] the candidate entries have the following intersections with the target subset definition:

* //foo.bar/01.tk - {'f'}
* //foo.bar/02.tk - {'P'}
* //foo.bar/04.tk - {'f', 'P'}
* //foo.bar/05.tk - {'f', 'P'}

The intersections for //foo.bar/01.tk and //foo.bar/02.tk are both strict subsets of //foo.bar/04.tk and //foo.bar/05.tk, so they don't meet selection
criteria. That leaves either //foo.bar/04.tk or //foo.bar/05.tk. Since //foo.bar/04.tk is listed first in the patch map it is selected.

* Step 8 - the selected patch, //foo.bar/04.tk, is fetched. Additionally as an optimization fetches are simultaneously started for the two glyph keyed
patches //foo.bar/01.gk and //foo.bar/04.gk. The table keyed patch //foo.bar/04.tk is partially invalidating which means the two glyph keyed patches
will be remain valid after the application of //foo.bar/04.tk. Thus the client can expect that the two glyph keyed patches will be required in future
iterations and begin the loads for those two patches at this time.

* Step 9 - The fetched patch //foo.bar/04.tk is applied to the initial font. This patch updates all tables except for glyf and loca
to add support for code points 'a' through 'Z'. Additionally the mapping in the "IFT " table is updated to the following:

<table>
<tr><th>Table = "IFT "</th><th colspan="2">Compatibility ID = 0x0000_0000_0000_0006</th></tr>
<tr><th>Subset Definition(s)</th><th>URI</th><th>Format Number</th>
<tr>
<td>
```
code points: { '0', '1', ..., '9' }
```
</td>
<td>//foo.bar/08.tk</td>
<td>
2, Table Keyed - Partial Invalidation
</td>
</tr>
</table>

Note the following changes:
* All entries that have data for 'a', ... 'z', and 'A', ..., 'Z' have been removed. Leaving just one entry for the remaining '0', ..., '9' code points.
* The font binary has changed and thus the previously listed table keyed patches are no longer valid. As a result the compatibility ID has changed and
the URI for the remaining '0', ..., '9' entry has changed. The patch at the new URI, //foo.bar/08.tk, is compatible with the updated font.

Iteration 2:

* Steps 2 through 6 - applying the [$Check entry intersection$] check to all entries in the updated font from the previous iteration, the following patch
entries intersect:
* //foo.bar/01.gk
* //foo.bar/04.gk

* Step 7 - one entry from that set must be picked. Only no invalidation patches remain, the client is free to pick either one. In this case it selects
the first one //foo.bar/01.gk.

* Step 8 - the fetch for //foo.bar/01.gk was previously started during iteration 1.

* Step 9 - The fetched patch //foo.bar/01.gk is applied to the current font subset. This patch adds glyph data for code points 'a' through 'm'
to the glyf and loca tables. Additionally the mapping in the "IFTX" table is updated to removed the entry for //foo.bar/01.gk. The compatibility ID
and URIs for other entries are unchanged.

Iteration 3:

* Steps 2 through 6 - applying the [$Check entry intersection$] check to all entries in the updated font from the previous iteration, the following patch
entries intersect:
* //foo.bar/04.gk

* Step 7 - one entry remains, it is selected.

* Step 8 - the fetch for //foo.bar/04.gk was previously started during iteration 1.

* Step 9 - The fetched patch //foo.bar/04.gk is applied to the current font subset. This patch adds glyph data for code points 'N' through 'Z'
to the glyf and loca tables. Additionally the mapping in the "IFTX" table is updated to removed the entry for //foo.bar/04.gk. The compatibility ID
and URIs for other entries are unchanged.


Iteration 4:

* Steps 2 through 6 - there are no remaining intersecting entries so the algorithm terminates. The font subset is returned and now ready to render any
content covered by the target subset definition.
Loading