nehan-player

twitter github

About

nehan-player is fast, powerful, extensible web-component player for paged-media powered by nehan (dynamic and logical html layout engine).

You can see Demo.

Quickstart


      <nehan-player writing-mode="horizontal-tb" width="responsive" height="500" font-size="16" layout="1x2">
          <div slot="content">
              <h1>Hello, nehan-player!</h1>
              <p>My first nehan-player experience!</p>
          </div>
      </nehan-player>
  
      <script src="https://cdn.jsdelivr.net/npm/nehan-player/dist/nehan-player.min.js"></script>
      <script>
          NehanPlayer.initialize();// Activate with default settings(no options).
      </script>
    

How to install

npm


      npm i nehan-player
    

cdn


      <script src="https://cdn.jsdelivr.net/npm/nehan-player/dist/nehan-player.min.js"></script>
    

How to use

1. Markup HTML

You can load text content by src attribute.


      <!-- use 'src' attribute -->
      <nehan-player src="path/to/text.html" writing-mode="horizontal-tb" width="800"
        height="500" font-size="16" layout="1x2">
      </nehan-player>
    

Or you can set text content by content slot.


      <!-- use 'content' slot -->
      <nehan-player writing-mode="horizontal-tb" width="800" height="500"
        font-size="16" layout="1x2">
          <div slot="content">Your content text is here</div>
      </nehan-player>
    

2. Activate nehan-player

To activate nehan-player, you should call NehanPlayer.initialize.

Typescript


      import { NehanPlayer } from 'nehan-player';

      NehanPlayer.initialize(); // Activate with default settings(no options).
    

Javascript


      require { NehanPlayer } from 'nehan-player';

      NehanPlayer.initialize(); // Activate with default settings(no options).
    

Note that there are many option argument for initialize method. (See NehanPlayer.initialize options)

Markup options

Supported element attributes

src

Specify path for the target text file.

Note that you can also set text content by content slot(See Supported slot)

theme

Specify path for the theme css(e.g. 'themes/default.css')

writing-mode

Specify "writing-mode" value of paged media content.

"horizontal-tb" or "vertical-rl" or "vertical-lr" are available.

width

Specify player width.

px size or "responsive" are available.

Note that min size of witdh is 300px.

If width is "responsive", player width automatically changes for each time the size of parent elmement is changed.

height

Specify player height(px).

Height of menu and footer are not included in this height.

Note that min size of height is 300px.

font-size

Specify document font size(px) for paged media content.

layout

Specify layout grid format.

"1x1" or "1x2" or "2x1" or "2x2" are available.

To use multi-column format(1x2 or 2x2), at least 600px of width is required. If width is smaller than 600px, then single column format is used.

To use multi-row format(2x1 or 2x2), at least 500px of height is required. If height is smaller than 500px, then single row format is used.

Supported template slot

content slot

You can use content slot to define player content.

Note that use can use src attribute for remote resource.


      <nehan-player>
          <div slot="content">
              <h1>This is my title!</h1>
              <p>This is my player content!!</p>
          </div>
      </nehan-player>
    

title slot

You can set the title at the top-left corner of screen by title slot.


          <nehan-player src="path/to/text">
              <span slot="title">This is my great title</span>
          </nehan-player>
        

sub-title slot

You can set the sub-title next to the title by sub-title slot.


          <nehan-player src="path/to/text">
              <span slot="title">This is my great title</span>
              <span slot="sub-title"> - chapter 1</span>
          </nehan-player>
        

button label slot

You can change the label of next/prev button by using goto-left-label slot or goto-right-label slot like this.


      <nehan-player src="path/to/text">
          <span slot="goto-left-label">goto left!!!</span>
          <span slot="goto-right-label">goto right!!!</span>
      </nehan-player>
    

footer slot

You can add footer text in the bottom-right corner of player by using footer slot.


      <nehan-player src="path/to/text">
          <span slot="footer">All rights reserved by <a href="#">xxx</a></span>
      </nehan-player>
    

NehanPlayer.initialize options

You can set extra settings or callbacks in optional arugment of NehanPlayer.initialize.

cssFiles

Set css files for shadow DOM of nehan-player.

Apart from the theme attribute of the elemement, this field is used to load other external css files.

Note that you can't put it in host html, because shadow DOM is not affected by host css.


      // if we use nehan-katex, we should import css for katex.
      NehanPlayer.initialize({
        cssFiles: ["katex.css"]
      });
    

cssText(since ver 0.1.0)

Inline style text for shadow DOM of nehan-player.


      // if we use nehan-katex, we should import css for katex.
      NehanPlayer.initialize({
        cssText: "#menu { display: none }"
      });
    

onCreateNehanStyles

Unlike normal HTML content, the content layout of each page is calculated by logical layout engine (nehan).

So if you need to add some style to page content, you should declare nehan style (CssStyleSheet declaration for nehan layout engine).

For more details of nehan style, see About nehan style section.

onFetchContent

Fired when the content of src attribute or content slot is fetched.

You can fix the content here for you own use.


      NehanPlayer.initialize({
        onFetchContent(src: string, content: string){
          if (src.endsWith(".txt")) {
            return content.replace(/^\n+/, "").replace(/\n/g, "<br>");
          }
          return content;
        }
      });
    

onPlayerReady

Fired when PagedMediaPlayer is ready to use.


      NehanPlayer.initialize({
        onPlayerReady(player: PagedMediaPlayer){
          // console.log("player ready:", player);
        }
      });
    

onParsePage

Fired when each page is parsed.


      NehanPlayer.initialize({
        onParsePage(player: PagedMediaPlayer, pageIndex: numuber){
          // console.log(`parsed page(${pageIndex})`);
        }
      });
    

onParseComplete

Fired when final page is parsed.

This is perfect timing to create outline(toc) element.


      import { PlayerSection }  from 'nehan-player';

      NehanPlayer.initialize({
        onParseComplete(player: PagedMediaPlayer, pageCount: numuber, ellapsedTime: number){
          // console.log(`finished! time = ${ellapsedTime / 1000}sec`);
          const outline = player.createOutline((section: PlayerSection) => {
            player.gotoPage(section.pageIndex);
          });
        }
      });
    

onSetPage

Fired when user move the page and new page is set.

If you want some action for each paging, you can add some action here.


      import { Page } from 'nehan';

      NehanPlayer.initialize({
        onSetPage(player: PagedMediaPlayer, page: Page){
          // console.log("set page:", page);
        }
      });
    

onClickLeftPage

Fired when user click left side of the page.


      NehanPlayer.initialize({
        onClickLeftPage(player: PagedMediaPlayer){
          player.gotoLeftPage();
        }
      });
    

onClickRightPage

Fired when user click right side of the page.


      NehanPlayer.initialize({
        onClickRightPage(player: PagedMediaPlayer){
          player.gotoRightPage();
        }
      });
    

PagedMediaPlayer interface

Properties

$host

HTMLElement of <nehan-player>.

id(readonly)

id attribute value of <nehan-player> element.

src(readonly)

src attribute value of <nehan-player> element.

content(readonly)

Content string of player.

currentSection(readonly)

Current section data.

currentPageIndex

Current page index that starts from zero.

You can set or get.


      const index = player.currentPageIndex; // get
      player.currentPageIndex = 0; // set(go to fist page)
    

pageCount

Total page count of document.

width(readonly)

Player width size in px.

height(readonly)

Player height size in px.

layout

Grid layout of player.

Returns "1x1" or "1x2" or "2x1" or "2x2"

fontSize

Base font-size used for paged-media content of player.

writingMode

writing-mode of this player.

Returns "horizontal-tb" or "vertical-rl" or "vertical-lr".


      const isVertical: boolean = player.writingMode.indexOf("vertical") >= 0;
      const isRightToLeft: boolean = player.writingMode === "vertical-rl";
    

Method

gotoPage(pageIndex: number)

Move to page of pageIndex.


      player.gotoPage(0); // goto first page
      player.gotoPage(player.pageCount - 1); // goto last page
    

gotoNextPage()

Goto next page.

If LTR text, it's same as gotoRightPage().

If RTL text, it's same as gotoLeftPage().

gotoPrevPage()

Goto previous page.

If LTR text, it's same as gotoLeftPage().

If RTL text, it's same as gotoRightPage().

gotoLeftPage()

Goto left side of the page.

If LTR text, it's same as gotoPrevPage().

If RTL text, it's same as gotoNextPage().

gotoRightPage()

Goto right side of the page.

If LTR text, it's same as gotoNextPage().

If RTL text, it's same as gotoPrevPage().

update(attributes)

Update player attribute and refresh player layout if required.

About supported attributes, see Supported attributes.


      player.update({ fontSize: 20 });
      player.update({ writingMode: "vertical-rl" });
      player.update({ width: "responsive", layout: "1x1" });
      player.update({ width: "900", height: "500" });
    

refresh(throttle: boolean)

Unlike update method, refresh method forces make player updated (update method is ignored if no change is detected).

If argument throttle is true, player prevents display from being updated too frequently.

createOutline(onClickTocItem?)

Create toc DOM tree.

Optional argument onClickTocItem is called when each toc item is clicked.

Note that if you call this method BEFORE onParseComplete callback, the toc tree will be incomplete one.


      import { PlayerSection } from 'nehan-player';

      NehanPlayer.initialize({
        onParseComplete(player: PagedMediaPlayer, pageCount: number, time: number){
          const $toc: HTMLElement = player.createOutline((section: PlayerSection) => player.gotoPage(section.pageIndex));
        }
      });
    

setTitle(title: string)

Set the top-left corner text.

setSubTitle(title: string)

Set the top-left corner sub-title text.


      NehanPayer.initialize({
        onSetPage(player: PagedMediaPlayer, page: Page) {
          const section = player.currentSection;
          if (!section.isRoot()) {
            player.setSubTitle(`&gt; ${section.title}`);
          }
        }
      });
    

About nehan style

You can create nehan style to modify inside style of page content.

Remember that each content of page is not calculated by native browser, but logical layout engine (nehan).

So if you need to modify the style of page content, you should declare nehan styles!

example1: back to top link

This is a tiny example of nehan style. By clicking link like <a href="#top">back to top</a>, we can goto first page, and it's color is green.


      import { CssStyleSheet, DomCallbackContext } from 'nehan';
      import { PagedMediaPlayer } from 'nehan-player';

      function createBackToTopStyle(player: PagedMediaPlayer){
        return new CssStyleSheet({
          "a[href='#top']": {
            "color": "green",
            "@create": (ctx: DomCallbackContext) => {
              ctx.dom.onclick = (e) => {
                e.preventDefault();
                player.gotoPage(0);
                return false;
              }
            }
          }
        });
      }
    

And you may be confused, because there is some strange property name. What is "@create"?

In nehan-style, the property name that starts with "@" is called callback style.

Each time the DOM of target selector is created, callback syle is called.

But you may wonder the word "each time". Isn't the DOM of selector associated with one node only created once?

No, it's not. Remember that nehan is "paged-media" layout engine, so one selector(for one node) can be generated into splited pages, and this callback function can be called multiple times for one node. So we use the word "each time".

Well, anyway, finally we got our first nehan style! Then we can set this style in onCreateNehanStyles.

And now you can use this "back to top" link anywhere in your page content.


      NehanPlayer.initialize({
        onCreateNehanStyles(player: PagedMediaPlayer){
          return [
            createBackToTopStyle(player), // our first nehan style!
          ];
        }
      });
    

example2: dynamic style

Unlike normal css, nehan-style can dynamically change the css value according to parent layout status.

The following is an example that dynamically creates page-break-before: always whether there is enough space left for the parent layout.

Note that in nehan, block size is expressed as extent (and inline size is expressed as measure).

And you may be confused again, because there is some strange property name once again. What is "!dynamic"?

In nehan-style, the property name that starts with "!" is called dynamic style.

This dynamic style runs each time before layout engine try to fill in the remaining space in the parent layout.


      function requiredExtent(requiredSize: number) {
        return (ctx: DynamicStyleContext): CssDeclarationBlock | undefined => {
          if (!ctx.parentContext) {
            return undefined;
          }
          if (ctx.parentContext.restExtent >= requiredSize) {
            return undefined; // enough block size is left!
          }
          // There is not enough block size left, so break the page.
          return { "page-break-before": "always" };
        };
      }

      const myStyleSheet = new CssStyleSheet({
        ".require-60": {
          "!dynamic": requiredExtent(60)
        }
      });      
    

Other examples

There are other plugins for nehan style, here is some examples.

About css propertie names in nehan-style

Be aware that nehan is a logical layout engine, so some propertie names of css are different from normal ones.

For example, margin-left is not available in nehan, instead, we use margin-start (for writing-mode:"horizontal-tb") or margin-after (for writing-mode:"vertical-rl").

Besides, border, padding, width, height, and direction(like left/right/top/bottom) are different from normal ones.

For more details, see "About logical size" and "About logical direction" section of README of nehan.

Demo

horizontal-tb, theme = default

Alice in Wonderland

vertical-rl, theme = skyblue

杜子春(芥川龍之介)

vertical-rl, theme = white

雪解(永井荷風)

display wikipedia, with toc

涅槃 - wikipedia